package com.xh.baselibrary.tts;

import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;

import com.xh.baselibrary.log.XLog;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;


/**
 * Created by Mr.X on 2017/1/11.
 */

public class TTSMediaPlay {
    private final static String TAG = TTSMediaPlay.class.getSimpleName();
    private AudioTrack mAudioTrack = null;
    private Context mContext;

    private static final int streamType = Build.VERSION.SDK_INT == 17 ? 13 : AudioManager.STREAM_SYSTEM;
    private static final float AUDIO_BUFF = Build.VERSION.SDK_INT == 17 ? 0.5f : 0.8f;

    private final int minBufferSize = AudioTrack.getMinBufferSize(16000,//每秒16K个点
            AudioFormat.CHANNEL_CONFIGURATION_MONO,//单声道
            AudioFormat.ENCODING_PCM_16BIT);//一个采样点16比特-2个字节

    private OnTTSPlayStateListener mStateListener;
    //开始播放
    public static final int PLAY_STATE_START = 0;
    //结束播报
    public static final int PLAY_STATE_END = 1;

    public static final int PLAY_STATE_STOPPED = 2;

    private volatile boolean isPlaying = false;

    private ExecutorService mExecutor = Executors.newSingleThreadExecutor();

    private Map<String, HashMap<Long, TTSResponderRunnable>> runnableMap = Collections.synchronizedMap(new HashMap<String, HashMap<Long, TTSResponderRunnable>>());

    private ReentrantLock mLock = new ReentrantLock();

    private Handler mainHandler;

    public TTSMediaPlay(Context context, OnTTSPlayStateListener stateListener) {
        mContext = context;
        mStateListener = stateListener;
        mainHandler = new Handler(Looper.getMainLooper());
    }

    public boolean isPlaying() {
        return isPlaying;
    }


    public void pauseTTS() {
        if (mAudioTrack != null && mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
            mAudioTrack.pause();
        }
    }

    public void continuePlayTTS() {
        if (mAudioTrack != null && mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
            mAudioTrack.play();
        }
    }


    private boolean initAudioTrack() {
        XLog.i(TAG, "init AudioTrack,minBufferSize==" + minBufferSize);
        if (mAudioTrack != null && mAudioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
            return true;
        }
        if (mAudioTrack != null) {
            mAudioTrack.release();
            mAudioTrack = null;
        }
        mAudioTrack = new AudioTrack(streamType,
                16000,
                AudioFormat.CHANNEL_CONFIGURATION_MONO,
                AudioFormat.ENCODING_PCM_16BIT,
                minBufferSize,
                AudioTrack.MODE_STREAM);
        if (mAudioTrack.getState() == AudioTrack.STATE_INITIALIZED) {
            return true;
        } else {
            mAudioTrack = null;
            return false;
        }
    }


    private void requestAudioFocus() {
        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        assert am != null;
        int result = am.requestAudioFocus(mAudioFocusListener, streamType, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
        XLog.i(TAG, "requestAudioFocus----result--->"+result);
        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            XLog.i(TAG, "requestAudioFocus----请求音频焦点成功");
            return;
        }
        XLog.i(TAG, "requestAudioFocus----请求音频焦点失败");
    }

    private AudioManager.OnAudioFocusChangeListener mAudioFocusListener = new AudioManager.OnAudioFocusChangeListener() {
        public void onAudioFocusChange(int focusChange) {
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_LOSS:
                    if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                        //失去音频焦点时触发的状态，且该状态应该会长期保持，此时应当暂停音频并释放音频相关的资源。
                        XLog.i(TAG, "OnAudioFocusChange----PLAYSTATE_PLAYING");
                        stopPlay();
                    }
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    XLog.i(TAG, "OnAudioFocusChange----AUDIOFOCUS_LOSS_TRANSIENT");
                    //失去音频焦点时触发的状态，但是该状态不会长时间保持，此时我们应该暂停音频，且当重新获取音频焦点的时候继续播放。
                    pauseTTS();
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    XLog.i(TAG, "OnAudioFocusChange----AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
                    //短暂失去焦点，先暂停。同时将标志位置成重新获得焦点后就开始播放
                    //失去音频焦点时触发的状态，在该状态的时候不需要暂停音频，但是我们应该降低音频的声音。
                    pauseTTS();
                    break;
                case AudioManager.AUDIOFOCUS_GAIN:
                    XLog.i(TAG, "OnAudioFocusChange----AUDIOFOCUS_GAIN");
                    //重新获得焦点，且符合播放条件，继续播放
                    continuePlayTTS();
                    break;
            }
        }
    };

    private void startPlay() {
        XLog.i(TAG, "startPlay");
        if (initAudioTrack()) {
            requestAudioFocus();
            isPlaying = true;
            mAudioTrack.play();
        }
    }


    private void stopPlay() {
        mLock.lock();
        try {
            XLog.i(TAG, "stopPlay");
            if (!isPlaying) {
                return;
            }
            isPlaying = false;
            if (mAudioTrack == null) {
                return;
            }
            if (mAudioTrack.getPlayState() != AudioTrack.STATE_UNINITIALIZED) {
                mAudioTrack.pause();
                mAudioTrack.stop();
                mAudioTrack.flush();
            }
            AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
            int i = am.abandonAudioFocus(mAudioFocusListener);
            XLog.i(TAG, "abandonAudioFocus----释放音频焦点----->" + i);
        } finally {
            mLock.unlock();
        }
    }

    void release() {
        mLock.lock();
        try {
            XLog.i(TAG, "releasePlay");
            if (mAudioTrack != null) {
                if (mAudioTrack.getPlayState() != AudioTrack.STATE_UNINITIALIZED) {
                    mAudioTrack.pause();
                    mAudioTrack.flush();
                }
                mAudioTrack.release();
            }
            mAudioTrack = null;
            stopAllResponse();
            mExecutor.shutdownNow();
        } finally {
            mLock.unlock();
        }
    }


    void addResponseQueue(TTSResponse response) {
        if (response == null) {
            return;
        }
        if (mExecutor == null) {
            return;
        }
        mLock.lock();
        try {
            String tag = response.getTag();
            if (runnableMap.containsKey(tag)) {
                HashMap<Long, TTSResponderRunnable> runnableHashMap = runnableMap.get(tag);
                TTSResponderRunnable runnable;
//                if (TTS.WELCOME_TAG.equals(tag) && response.getNumSamples() > 0) {
//                    runnable = new WelcomeTTSRunnable(response);
//                } else {
//                    runnable = new TTSResponderRunnable(response);
//                }
                runnable = new TTSResponderRunnable(response);
                runnableHashMap.put(response.getTimeID(), runnable);
                mExecutor.execute(runnable);
            } else {
                HashMap<Long, TTSResponderRunnable> runnableHashMap = new HashMap<>();
                TTSResponderRunnable runnable;
//                if (TTS.WELCOME_TAG.equals(tag) && response.getNumSamples() > 0) {
//                    runnable = new WelcomeTTSRunnable(response);
//                } else {
//                    runnable = new TTSResponderRunnable(response);
//                }
                runnable = new TTSResponderRunnable(response);
                runnableHashMap.put(response.getTimeID(), runnable);
                runnableMap.put(tag, runnableHashMap);
                mExecutor.execute(runnable);
            }
        } finally {
            mLock.unlock();
        }
    }


    void stopAllResponse() {
        mLock.lock();
        try {
            Iterator<Map.Entry<String, HashMap<Long, TTSResponderRunnable>>> it = runnableMap.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, HashMap<Long, TTSResponderRunnable>> entry = it.next();
                Map<Long, TTSResponderRunnable> requestMap = entry.getValue();
                Iterator<Map.Entry<Long, TTSResponderRunnable>> iterator = requestMap.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<Long, TTSResponderRunnable> requestEntry = iterator.next();
                    requestEntry.getValue().stopWrite();
                    iterator.remove();
                }
                it.remove();
            }
        } finally {
            mLock.unlock();
        }
    }

    void stopPlay(String tag) {
        if (TextUtils.isEmpty(tag)) {
            return;
        }
        mLock.lock();
        try {
            HashMap<Long, TTSResponderRunnable> map = runnableMap.remove(tag);
            if (map != null) {
                Iterator<Map.Entry<Long, TTSResponderRunnable>> it = map.entrySet().iterator();
                while (it.hasNext()) {
                    Map.Entry<Long, TTSResponderRunnable> entry = it.next();
                    entry.getValue().stopWrite();
                    it.remove();
                }
            }
        } finally {
            mLock.unlock();
        }
    }

    void stopPlay(String tag, long timeId) {
        if (TextUtils.isEmpty(tag)) {
            return;
        }
        mLock.lock();
        try {
            HashMap<Long, TTSResponderRunnable> map = runnableMap.get(tag);
            if (map != null) {
                TTSResponderRunnable responderRunnable = map.remove(timeId);
                if (responderRunnable != null) {
                    responderRunnable.stopWrite();
                }
            }
        } finally {
            mLock.unlock();
        }
    }

    void addWarningResponse(TTSResponse response) {
        if (response == null) {
            return;
        }
        new Thread(new WarningRunnable(response)).start();
    }


    private class WarningRunnable implements Runnable {
        private TTSResponse mResponse;
        private AudioTrack warningAudioTrack = null;

        public WarningRunnable(TTSResponse response) {
            this.mResponse = response;
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
        }

        @Override
        public void run() {
            warningAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                    16000,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minBufferSize,
                    AudioTrack.MODE_STREAM);
            XLog.i(TAG, "warningAudioTrack.getState()--->" + warningAudioTrack.getState());
            if (warningAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED) {
                XLog.i(TAG, "warningAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED");
                return;
            }
            warningAudioTrack.play();
            XLog.i(TAG, "warningAudioTrack.play()--->");
            while (true) {
                TTSResponse.ResponseData data;
                try {
                    data = mResponse.pcmDeque.take();
                    if (data != null) {
                        writeVoice(data.getBytes(), warningAudioTrack);
                        if (data.isFinish()) {
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    XLog.i(TAG, "InterruptedException----->" + mResponse.toString());
                    break;
                }
            }
            XLog.i(TAG, "warningAudioTrack.playFinish()--->");
            warningAudioTrack.flush();
            warningAudioTrack.stop();
            warningAudioTrack.release();
        }

        private void writeVoice(byte[] voice, AudioTrack audioTrack) {
            if (voice == null || voice.length == 0) {
                return;
            }
            //XLog.i(TAG, "WriteVoice，voice.length==" + voice.length);
            final int L = voice.length;
            final int bufferSize = (int) (minBufferSize * AUDIO_BUFF);
            if (L > bufferSize) {
                final int times = L / bufferSize;
                //XLog.i(TAG, "WriteVoice，times==" + times);
                int dstPos = 0;
                for (int i = 0; i <= times; i++) {
                    byte[] bytes = new byte[bufferSize];
                    int length = bufferSize;
                    if (dstPos + bytes.length >= voice.length) {
                        length = voice.length - dstPos;
                    }
                    // XLog.i(TAG, "WriteVoice，length==" + length);
                    System.arraycopy(voice, dstPos, bytes, 0, length);

                    if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                        audioTrack.write(bytes, 0, bytes.length);
                    } else {
                        break;
                    }
                    dstPos += bytes.length;
                }
            } else {
                if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                    audioTrack.write(voice, 0, voice.length);
                }
            }
        }
    }


    private class TTSResponderRunnable extends Thread {
        //生成的结果数组
        private TTSResponse mResponse;

        public TTSResponderRunnable(TTSResponse response) {
            super(TTSResponderRunnable.class.getSimpleName());
            mResponse = response;
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
        }

        private volatile boolean isStop = false;

        void stopWrite() {
            XLog.i(TAG, "stopWrite()----->mResponse=" + mResponse);
            isStop = true;
            mResponse.pcmDeque.clear();
            this.interrupt();
        }

        @Override
        public void run() {
            startPlay();
            if (mStateListener != null) {
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mStateListener.onStartPlay(mResponse);
                    }
                });
            }

            while (!isStop&&!isInterrupted()) {
                TTSResponse.ResponseData data;
                try {
                    data = mResponse.pcmDeque.take();
                    writeVoice(data.getBytes());
                    if (data.isFinish()) {
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    isStop = true;
                    XLog.i(TAG, "InterruptedException----->" + mResponse.toString());
                    break;
                }
            }
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stopPlay();
            if (isStop) {
                if (mStateListener != null) {
                    mainHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mStateListener.onStoppedPlay(mResponse);
                        }
                    });
                }
            } else {
                if (mStateListener != null) {
                    mainHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mStateListener.onFinishPlay(mResponse);
                        }
                    });
                }
            }
            mLock.lock();
            try {
                HashMap<Long, TTSResponderRunnable> runnableHashMap = runnableMap.get(mResponse.getTag());
                if (runnableHashMap != null) {
                    runnableHashMap.remove(mResponse.getTimeID());
                    if (runnableHashMap.isEmpty()) {
                        runnableMap.remove(mResponse.getTag());
                    }
                }
            } finally {
                mLock.unlock();
            }
        }


        private void writeVoice(byte[] voice) {
            if (voice == null || voice.length == 0) {
                return;
            }

            //XLog.i(TAG, "WriteVoice，voice.length==" + voice.length);
            final int L = voice.length;
            final int bufferSize = (int) (minBufferSize * AUDIO_BUFF);
            if (L > bufferSize) {
                final int times = L / bufferSize;
                //XLog.i(TAG, "WriteVoice，times==" + times);
                int dstPos = 0;
                for (int i = 0; i <= times; i++) {
                    if (isStop) {
                        return;
                    }
                    byte[] bytes = new byte[bufferSize];
                    int length = bufferSize;
                    if (dstPos + bytes.length >= voice.length) {
                        length = voice.length - dstPos;
                    }
                    // XLog.i(TAG, "WriteVoice，length==" + length);
                    System.arraycopy(voice, dstPos, bytes, 0, length);
                    if (mAudioTrack != null && mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
//                        long start1 = System.currentTimeMillis();
                        int bytesWritten = mAudioTrack.write(bytes, 0, bytes.length);
//                        long end1 = System.currentTimeMillis();
//                        Log.i(TAG, "Decoder-write else ," + bytesWritten + "," + (end1 - start1));
                    } else {
                        break;
                    }
                    dstPos += bytes.length;
                }
            } else {
                if (mAudioTrack != null && mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
//                    long start1 = System.currentTimeMillis();
                    int bytesWritten = mAudioTrack.write(voice, 0, voice.length);
//                    long end1 = System.currentTimeMillis();
//                    Log.i(TAG, "Decoder-write else ," + bytesWritten + "," + (end1 - start1));
                }
            }
        }
    }

    private class WelcomeTTSRunnable extends TTSResponderRunnable {
        private TTSResponse mResponse;
        private AudioTrack welcomeAudioTrack = null;

        public WelcomeTTSRunnable(TTSResponse response) {
            super(response);
            this.mResponse = response;
        }

        private volatile boolean isStop = false;

        @Override
        void stopWrite() {
            XLog.i(TAG, "stopWrite()----->mResponse=" + mResponse);
            isStop = true;
            mResponse.pcmDeque.clear();
            this.interrupt();
        }

        @Override
        public void run() {
            welcomeAudioTrack = new AudioTrack(streamType,
                    16000,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minBufferSize,
                    AudioTrack.MODE_STREAM);
            XLog.i(TAG, "welcomeAudioTrack.getState()--->" + welcomeAudioTrack.getState());
            if (welcomeAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED) {
                XLog.i(TAG, "welcomeAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED");
                if (mStateListener != null) {
                    mainHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mStateListener.onStoppedPlay(mResponse);
                        }
                    });
                }
                return;
            }
//            requestAudioFocus();
            isPlaying = true;
            welcomeAudioTrack.play();
            if (mStateListener != null) {
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mStateListener.onStartPlay(mResponse);
                    }
                });
            }
            int numSamples = mResponse.getNumSamples();
            XLog.i(TAG, "numSamples--->" + numSamples);
            welcomeAudioTrack.setNotificationMarkerPosition(numSamples - 10);
            welcomeAudioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() {
                @Override
                public void onMarkerReached(AudioTrack track) {
                    XLog.i(TAG, "onMarkerReached--->");
                    if (mStateListener != null) {
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mStateListener.onFinishPlay(mResponse);
                            }
                        });
                    }
                    if (welcomeAudioTrack == null) {
                        return;
                    }
                    welcomeAudioTrack.setPlaybackPositionUpdateListener(null);
                    if (welcomeAudioTrack.getPlayState() != AudioTrack.STATE_UNINITIALIZED) {
                        welcomeAudioTrack.pause();
                        welcomeAudioTrack.stop();
                        welcomeAudioTrack.flush();
                        welcomeAudioTrack.release();
                    }
//                    AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
//                    XLog.i(TAG, "abandonAudioFocus----释放音频焦点  " + am.abandonAudioFocus(mAudioFocusListener));
                }

                @Override
                public void onPeriodicNotification(AudioTrack track) {
                    XLog.i(TAG, "track.getNotificationMarkerPosition()--->" + track.getNotificationMarkerPosition());
                }
            });
            while (!isStop) {
                TTSResponse.ResponseData data;
                try {
                    data = mResponse.pcmDeque.take();
                    if (data != null) {
                        writeVoice(data.getBytes());
                        if (data.isFinish()) {
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    isStop = true;
                    XLog.i(TAG, "InterruptedException----->" + mResponse.toString());
                    break;
                }
            }
            XLog.i(TAG, "writeVoice_Finish----->WelcomeTTSRunnable-----isStop=" + isStop);
            if (isStop) {
                if (mStateListener != null) {
                    mainHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mStateListener.onStoppedPlay(mResponse);
                        }
                    });
                }
                if (welcomeAudioTrack == null) {
                    return;
                }
                if (welcomeAudioTrack.getPlayState() != AudioTrack.STATE_UNINITIALIZED) {
                    welcomeAudioTrack.stop();
                    welcomeAudioTrack.release();
                }
            }
            mLock.lock();
            try {
                HashMap<Long, TTSResponderRunnable> runnableHashMap = runnableMap.get(mResponse.getTag());
                if (runnableHashMap != null) {
                    runnableHashMap.remove(mResponse.getTimeID());
                    if (runnableHashMap.isEmpty()) {
                        runnableMap.remove(mResponse.getTag());
                    }
                }
            } finally {
                mLock.unlock();
            }
        }

        private void writeVoice(byte[] voice) {
            if (voice == null || voice.length == 0) {
                return;
            }

            //XLog.i(TAG, "WriteVoice，voice.length==" + voice.length);
            final int L = voice.length;
            final int bufferSize = (int) (minBufferSize * AUDIO_BUFF);
            if (L > bufferSize) {
                final int times = L / bufferSize;
                //XLog.i(TAG, "WriteVoice，times==" + times);
                int dstPos = 0;
                for (int i = 0; i <= times; i++) {
                    if (isStop) {
                        return;
                    }
                    byte[] bytes = new byte[bufferSize];
                    int length = bufferSize;
                    if (dstPos + bytes.length >= voice.length) {
                        length = voice.length - dstPos;
                    }
                    // XLog.i(TAG, "WriteVoice，length==" + length);
                    System.arraycopy(voice, dstPos, bytes, 0, length);
                    if (welcomeAudioTrack != null && welcomeAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                        welcomeAudioTrack.write(bytes, 0, bytes.length);
                    } else {
                        break;
                    }
                    dstPos += bytes.length;
                }
            } else {
                if (welcomeAudioTrack != null && welcomeAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                    welcomeAudioTrack.write(voice, 0, voice.length);
                }
            }
        }

    }


    interface OnTTSPlayStateListener {
        void onStartPlay(TTSResponse response);

        void onFinishPlay(TTSResponse response);

        void onStoppedPlay(TTSResponse response);
    }
}
